package com.jay.media.music.core.player

import android.annotation.SuppressLint
import android.content.Context
import android.media.MediaMetadata
import android.media.browse.MediaBrowser
import android.media.session.PlaybackState
import android.text.TextUtils
import com.blankj.utilcode.util.SPUtils
import com.jay.media.kit.LogHelper
import com.jay.media.kit.RandomUtils
import com.jay.media.music.core.constant.Keys
import com.jay.media.music.core.db.MusicManager
import com.jay.media.music.core.provider.METADATA_FAVOR
import com.jay.media.music.core.provider.METADATA_TRACK_SOURCE
import com.jay.media.music.core.provider.MusicIDHelper
import com.jay.media.music.core.provider.MusicProvider
import com.jay.media.music.core.util.MusicExt.toMediaMetadata
import java.util.concurrent.CopyOnWriteArrayList

/**
 * @author xuzhou4
 * @date 2022/3/17
 */
class MusicPlayManager {

    companion object {
        private val TAG = LogHelper.makeLogTag(MusicPlayManager::class.java)
        private val INSTANCE by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
            MusicPlayManager()
        }

        fun get(): MusicPlayManager {
            return INSTANCE
        }
    }

    private lateinit var mContext: Context
    private lateinit var mPlayer: LocalAudioPlayer
    private lateinit var mMusicProvider: MusicProvider
    private var mPlaybackState = PlaybackState.STATE_NONE
    private var mCallBacks: MutableList<MusicPlayListener> = CopyOnWriteArrayList()

    private var mPlayIndex: Int = 0
    private var mPlayMode: PlayMode = PlayMode.LIST
    private var mPlayList: MutableList<MediaMetadata> = CopyOnWriteArrayList()

    fun init(context: Context) {
        mContext = context
        mPlayer = LocalAudioPlayer(context)
        mPlayer.setPlayerEventListener(mPlayListener)

        mMusicProvider = MusicProvider(context)
        mMusicProvider.retrieveMediaAsync(object : MusicProvider.Callback {
            override fun onScanFinish(success: Boolean) {
                LogHelper.d(TAG, "on scan finish $success")
                restore()
            }
        })
    }

    @SuppressLint("WrongConstant")
    fun restore(): MediaMetadata? {
        val playList =
            MusicManager.get().queryPlayList().filter { m -> mMusicProvider.getMusic(m.id) != null }
                .map { m -> m.toMediaMetadata() }.toMutableList()
        if (playList.isNotEmpty()) {
            mPlayList = playList

            val musicId = SPUtils.getInstance().getString(Keys.KEY_LAST_PLAY_MUSIC_ID)
            if (!TextUtils.isEmpty(musicId)) {
                val item = mMusicProvider.getMusic(musicId)

                var playIndex = 0
                for (i in mPlayList.indices) {
                    if (mPlayList[i].getString(MediaMetadata.METADATA_KEY_MEDIA_ID) == item?.getString(
                            MediaMetadata.METADATA_KEY_MEDIA_ID
                        )
                    ) {
                        playIndex = i
                        break
                    }
                }

                playIndex(playIndex, false)
                return item
            }
        }

        return null
    }

    fun play(mediaId: String) {
        LogHelper.d(TAG, "play() called with: mediaId = $mediaId")

        val musicId = MusicIDHelper.extractMusicIDFromMediaID(mediaId)
        LogHelper.d(TAG, "musicId -> $musicId")

        val mediaIndex = getMediaMetadataIndex(musicId)
        LogHelper.d(TAG, "media index $mediaIndex")
        if (mediaIndex >= 0) {
            playIndex(mediaIndex)
        } else {
            val mediaMetadata = mMusicProvider.getMusic(musicId)
            mediaMetadata?.let {
                addPlayList(it)
                playIndex(mPlayList.size - 1)
            }
        }
    }

    private fun getMediaMetadataIndex(musicId: String?): Int {
        for (i in mPlayList.indices) {
            if (musicId == mPlayList[i].getString(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
                return i
            }
        }
        return -1
    }

    @SuppressLint("WrongConstant")
    fun play(mediaMetadata: MediaMetadata, play: Boolean = true) {
        val path = mediaMetadata.getString(METADATA_TRACK_SOURCE) ?: ""
        LogHelper.d(TAG, "play music path -> $path")

        mPlayer.prepare(path)
        if (play) {
            mPlayer.play()
        }

        notifyMediaMetadataChange(mediaMetadata)
        MusicManager.get().insertPlayHistory(mediaMetadata)

        SPUtils.getInstance().put(
            Keys.KEY_LAST_PLAY_MUSIC_ID,
            mediaMetadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)
        )
    }

    private fun playIndex(index: Int, play: Boolean = true) {
        LogHelper.d(TAG, "play index $index")
        if (index >= 0 && index < mPlayList.size) {
            if (mPlayIndex != index || mPlaybackState == PlaybackState.STATE_NONE
                || mPlaybackState == PlaybackState.STATE_STOPPED
            ) {
                mPlayIndex = index
                play(mPlayList[index], play)
            } else if (mPlaybackState == PlaybackState.STATE_PAUSED) {
                resume()
            }
        }
    }

    fun play() {
        LogHelper.d(TAG, "play() called")
        mPlayer.play()
    }

    fun resume() {
        LogHelper.d(TAG, "resume() called")
        mPlayer.play()
    }

    fun isPlaying(): Boolean {
        return mPlaybackState == PlaybackState.STATE_PLAYING
    }

    fun pause() {
        LogHelper.d(TAG, "pause() called")
        mPlayer.pause()
    }

    fun playOrPause() {
        LogHelper.d(TAG, "playOrPause() called : $mPlaybackState")
        if (isPlaying()) {
            pause()
        } else {
            resume()
        }
    }

    fun seekTo(progress: Long) {
        LogHelper.d(TAG, "seekTo() called with: progress = $progress")
        mPlayer.seekTo(progress)
    }

    fun prev() {
        LogHelper.d(TAG, "prev() called")
        val prevPlayIndex = getPrevPlayIndex()
        LogHelper.d(TAG, "prev playMode $mPlayMode, index : $prevPlayIndex")
        if (prevPlayIndex >= 0) {
            playIndex(prevPlayIndex)
        }
    }

    fun next() {
        LogHelper.d(TAG, "next() called")
        val nextPlayIndex = getNextPlayIndex()
        LogHelper.d(TAG, "next playMode $mPlayMode, index : $nextPlayIndex")
        if (nextPlayIndex >= 0) {
            playIndex(nextPlayIndex)
        }
    }

    fun getContentPosition(): Long {
        return mPlayer.getContentPosition()
    }

    fun getPlaySpeed(): Float {
        return mPlayer.getSpeed()
    }

    fun getAudioSessionId(): Int {
        return mPlayer.getAudioSessionId()
    }

    fun getCurrMediaMetadata(): MediaMetadata? {
        return if (mPlayList.isEmpty()) null else mPlayList[mPlayIndex]
    }

    fun getPlayList(): MutableList<MediaMetadata> {
        return mPlayList
    }

    private var mPlayListener = object : IPlayerEventListener {
        override fun onPlayStateChanged(state: Int) {
            LogHelper.d(TAG, "onPlayStateChanged() called with: state = $state")
            setPlaybackState(state)
        }

        override fun onBufferedSeekChanged(seek: Long, total: Long) {
            LogHelper.d(TAG, "onBufferedSeekChanged() called with: seek = $seek, total = $total")
            setPlaybackState(mPlaybackState)
        }

        override fun onPlayedSeekChanged(seek: Long, total: Long) {
            LogHelper.d(TAG, "onPlayedSeekChanged() called with: seek = $seek, total = $total")
            setPlaybackState(mPlaybackState)
        }

        override fun onPlayCompleted() {
            LogHelper.d(TAG, "onPlayCompleted() called")
            next()
        }

        override fun onPlayError(code: Int) {
            LogHelper.d(TAG, "onPlayError() called with: code = $code")
        }
    }

    private fun setPlaybackState(playbackState: Int) {
        mPlaybackState = playbackState
        notifyPlaybackStateChange(
            PlaybackState.Builder().setState(
                playbackState,
                mPlayer.getContentPosition(),
                mPlayer.getSpeed()
            ).build()
        )
    }

    fun registerPlayCallBack(musicPlayListener: MusicPlayListener) {
        if (!mCallBacks.contains(musicPlayListener)) {
            mCallBacks.add(musicPlayListener)
        }
    }

    fun unregisterPlayCallBack(musicPlayListener: MusicPlayListener) {
        mCallBacks.remove(musicPlayListener)
    }

    @SuppressLint("WrongConstant")
    fun notifyMediaMetadataChange(media: MediaMetadata?) {
        val metadata = MediaMetadata.Builder(media)
            .putString(METADATA_FAVOR, MusicManager.get().isFavor(media!!).toString())
            .build()
        for (callback in mCallBacks) {
            callback.onMediaMetadataChange(metadata)
        }
    }

    private fun notifyPlaybackStateChange(state: PlaybackState) {
        for (callback in mCallBacks) {
            callback.onPlaybackStateChange(state)
        }
    }

    private fun notifyMediaMetadataChange(mediaList: List<MediaMetadata>) {
        for (callback in mCallBacks) {
            callback.onMediaMetadataListChange(mediaList)
        }
    }

    fun getChildren(parentId: String): MutableList<MediaBrowser.MediaItem> {
        return mMusicProvider.getChildren(parentId, mContext.resources)
    }

    fun operateFavor(musicId: String?, favor: Boolean) {
        val music = mMusicProvider.getMusic(musicId)
        music?.let {
            MusicManager.get().operateFavor(it, favor)
        }
    }

    fun addPlayList(musicId: String) {
        LogHelper.d(TAG, "add play list mediaId $musicId")
        val music = mMusicProvider.getMusic(musicId)
        music?.let {
            addPlayList(music)
        }
    }

    fun addPlayList(media: MediaMetadata) {
        var alreadyAddToList = false
        for (m in mPlayList) {
            if (media.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) == m.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
                alreadyAddToList = true
                break
            }
        }

        if (!alreadyAddToList) {
            mPlayList.add(media)
            notifyMediaMetadataChange(mPlayList)

            MusicManager.get().insertPlayList(media)
        }
    }

    fun removePlayList(musicId: String) {
        LogHelper.d(TAG, "remove play list mediaId $musicId")
        val music = mMusicProvider.getMusic(musicId)
        music?.let {
            removePlayList(music)
        }
    }

    fun removePlayList(media: MediaMetadata) {
        for (m in mPlayList) {
            if (media.getString(MediaMetadata.METADATA_KEY_MEDIA_ID) == m.getString(MediaMetadata.METADATA_KEY_MEDIA_ID)) {
                mPlayList.remove(m)
                break
            }
        }
        notifyMediaMetadataChange(mPlayList)

        MusicManager.get().removePlayList(media)
    }

    private fun getNextPlayIndex(): Int {
        if (mPlayList.size <= 0) {
            return -1
        }

        if (mPlayList.size == 1) {
            return 0
        }

        return when (mPlayMode) {
            PlayMode.LIST -> {
                if (mPlayIndex + 1 >= mPlayList.size) {
                    0
                } else {
                    mPlayIndex + 1
                }
            }
            PlayMode.ONE_LOOP -> mPlayIndex
            PlayMode.SHUFFLE -> {
                RandomUtils.random(mPlayList.size, mPlayIndex, 3)
            }
            else -> -1
        }
    }

    private fun getPrevPlayIndex(): Int {
        if (mPlayList.size <= 0) {
            return -1
        }

        if (mPlayList.size == 1) {
            return 0
        }

        return when (mPlayMode) {
            PlayMode.LIST -> {
                if (mPlayIndex - 1 < 0) {
                    mPlayList.size - 1
                } else {
                    mPlayIndex - 1
                }
            }
            PlayMode.ONE_LOOP -> mPlayIndex
            PlayMode.SHUFFLE -> {
                RandomUtils.random(mPlayList.size, mPlayIndex, 3)
            }
            else -> -1
        }
    }
}